package com.bx.implatform.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.model.User;
import com.bx.imcommon.util.CommaTextUtils;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.LogConstants;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Group;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.entity.HgAlarmResponseRecord;
import com.bx.implatform.entity.PhgLog;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.GroupMapper;
import com.bx.implatform.mapper.GroupMessageMapper;
import com.bx.implatform.mapper.HgAlarmResponseRecordMapper;
import com.bx.implatform.service.IFriendService;
import com.bx.implatform.service.IGroupMemberService;
import com.bx.implatform.service.IGroupService;
import com.bx.implatform.service.IPhgLogService;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.GroupAccidentInviteVO;
import com.bx.implatform.vo.GroupInviteVO;
import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.GroupVO;
import com.bx.implatform.vo.UserVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

@Slf4j
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP)
@Service
@RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements IGroupService {
    private final IUserService userService;
    private final IGroupMemberService groupMemberService;
    private final GroupMessageMapper groupMessageMapper;
    private final IFriendService friendsService;
    private final IMClient imClient;
    private final HgAlarmResponseRecordMapper hgAlarmResponseRecordMapper;
    private final IPhgLogService phgLogService;


    private final RedisTemplate<String, Object> redisTemplate;

    private static @NotNull PhgLog getGroupLog(GroupVO vo, User user, String accidentId) {
        Date nowDate = new Date();
        PhgLog groupLog = new PhgLog();
        groupLog.setTitle(LogConstants.TITLE_CREATE_GROUP);
        groupLog.setContent(String.format(LogConstants.CONTENT_CREATE_GROUP,
                user.getNickName(), DateTimeUtils.getFormatDate(nowDate, DateTimeUtils.FULL_DATE_FORMAT), vo.getName(), user.getNickName()));
        return getAccidentCommonLog(user, accidentId, nowDate, groupLog);
    }

    @NotNull
    private static PhgLog getAccidentCommonLog(User user, String accidentId, Date nowDate, PhgLog groupLog) {
        groupLog.setForeignKey(accidentId);
        groupLog.setType(LogConstants.TYPE_TEXT);
        groupLog.setCreateTime(nowDate);
        groupLog.setUpdateTime(nowDate);
        groupLog.setPrefixKey(LogConstants.KEY_HG_ALARM_RESPONSE_RECORD);
        groupLog.setPersonId(String.valueOf(user.getId()));
        return groupLog;
    }

    private static @NotNull PhgLog getStartAccidentLog(User user, HgAlarmResponseRecord hgAlarmResponseRecord, String accidentId) {
        PhgLog phgLog = new PhgLog();
        Date nowDate = new Date();
        phgLog.setTitle(LogConstants.TITLE_START_ALARM_RESPONSE_RECORD);
        phgLog.setContent(String.format(LogConstants.CONTENT_START_ALARM_RESPONSE_RECORD,
                user.getNickName(), DateTimeUtils.getFormatDate(nowDate, DateTimeUtils.FULL_DATE_FORMAT), hgAlarmResponseRecord.getAccidentName()));
        return getAccidentCommonLog(user, accidentId, nowDate, phgLog);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public GroupVO createGroup(GroupVO vo) {
        UserSession session = SessionContext.getSession();
        User user = userService.getById(session.getUserId());
        String accidentId = vo.getAccidentId();
        List<Group> groupList = this.list(new QueryWrapper<Group>().eq("accident_id",accidentId));
        if (CollectionUtils.isEmpty(groupList)) {
            HgAlarmResponseRecord hgAlarmResponseRecord = hgAlarmResponseRecordMapper.selectById(accidentId);
            vo.setName(hgAlarmResponseRecord.getAccidentName() + "应急群聊");
            LinkedList<PhgLog> phgLogLinkedList = new LinkedList<>();
            // 事件启动日志
            PhgLog phgLog = getStartAccidentLog(user, hgAlarmResponseRecord, accidentId);
            //创建群组日志
            PhgLog groupLog = getGroupLog(vo, user, accidentId);
            phgLogLinkedList.add(phgLog);
            phgLogLinkedList.add(groupLog);
            phgLogService.saveBatch(phgLogLinkedList);
            hgAlarmResponseRecord.setOperation("1");
            hgAlarmResponseRecordMapper.updateById(hgAlarmResponseRecord);
        }else {
            //如果群组已存在，则直接返回
            return null;
        }
        // 保存群组数据
        Group group = BeanUtils.copyProperties(vo, Group.class);
        group.setOwnerId(user.getId());
        this.save(group);
        // 把群主加入群
        GroupMember groupMember = new GroupMember();
        groupMember.setGroupId(group.getId());
        groupMember.setUserId(user.getId());
//        groupMember.setHeadImage(user.getHeadImageThumb());
        groupMember.setAliasName(StringUtils.isEmpty(vo.getAliasName()) ? session.getNickName() : vo.getAliasName());
        groupMember.setRemark(StringUtils.isEmpty(vo.getRemark()) ? group.getName() : vo.getRemark());
        groupMemberService.save(groupMember);
        vo.setId(group.getId());
        vo.setAliasName(groupMember.getAliasName());
        vo.setRemark(groupMember.getRemark());
        // 邀请消息通知bot进群
        Long botUserId = this.inviteBotMessage(vo);
        vo.setBotUserId(botUserId);
        log.info("创建群聊，群聊id:{},群聊名称:{}", group.getId(), group.getName());
        return vo;
    }

    @CacheEvict(value = "#vo.getId()")
    @Transactional(rollbackFor = Exception.class)
    @Override
    public GroupVO modifyGroup(GroupVO vo) {
        UserSession session = SessionContext.getSession();
        // 校验是不是群主，只有群主能改信息
        Group group = this.getById(vo.getId());
        // 群主有权修改群基本信息
        if (group.getOwnerId().equals(session.getUserId())) {
            group = BeanUtils.copyProperties(vo, Group.class);
            this.updateById(group);
        }
        // 更新成员信息
        GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(), session.getUserId());
        if (Objects.isNull(member) || member.getQuit()) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群聊的成员");
        }
        member.setAliasName(StringUtils.isEmpty(vo.getAliasName()) ? session.getNickName() : vo.getAliasName());
        member.setRemark(StringUtils.isEmpty(vo.getRemark()) ? Objects.requireNonNull(group).getName() : vo.getRemark());
        groupMemberService.updateById(member);
        log.info("修改群聊，群聊id:{},群聊名称:{}", group.getId(), group.getName());
        return vo;
    }

    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = "#groupId")
    @Override
    public void deleteGroup(Long groupId) {
        UserSession session = SessionContext.getSession();
        Group group = this.getById(groupId);
        if (!group.getOwnerId().equals(session.getUserId())) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "只有群主才有权限解除群聊");
        }
        // 群聊用户id
        List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId);
        // 逻辑删除群数据
        group.setDeleted(true);
        this.updateById(group);
        // 删除成员数据
        groupMemberService.removeByGroupId(groupId);
        // 清理已读缓存
        String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
        redisTemplate.delete(key);
        // 推送解散群聊提示
        this.sendTipMessage(groupId, userIds, String.format("'%s'解散了群聊", session.getNickName()));
        log.info("删除群聊，群聊id:{},群聊名称:{}", group.getId(), group.getName());
    }

    @Override
    public void quitGroup(Long groupId) {
        Long userId = SessionContext.getSession().getUserId();
        Group group = this.getById(groupId);
        if (group.getOwnerId().equals(userId)) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "您是群主，不可退出群聊");
        }
        // 删除群聊成员
        groupMemberService.removeByGroupAndUserId(groupId, userId);
        // 清理已读缓存
        String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
        redisTemplate.opsForHash().delete(key, userId.toString());
        // 推送退出群聊提示
        this.sendTipMessage(groupId, Arrays.asList(userId), "您已退出群聊");
        log.info("退出群聊，群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
    }

    @Override
    public void kickGroup(Long groupId, Long userId) {
        UserSession session = SessionContext.getSession();
        Group group = this.getById(groupId);
        if (!group.getOwnerId().equals(session.getUserId())) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群主，没有权限踢人");
        }
        if (userId.equals(session.getUserId())) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "亲，不能移除自己哟");
        }
        // 删除群聊成员
        groupMemberService.removeByGroupAndUserId(groupId, userId);
        // 清理已读缓存
        String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
        redisTemplate.opsForHash().delete(key, userId.toString());
        // 推送踢出群聊提示
        this.sendTipMessage(groupId, Arrays.asList(userId), "您已被移出群聊");
        log.info("踢出群聊，群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
    }

    @Override
    public GroupVO findById(Long groupId) {
        UserSession session = SessionContext.getSession();
        Group group = super.getById(groupId);
        if (Objects.isNull(group)) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在");
        }
        GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId());
        if (Objects.isNull(member)) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "您未加入群聊");
        }
        GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
        vo.setAliasName(member.getAliasName());
        vo.setRemark(member.getRemark());
        vo.setQuit(member.getQuit());
        return vo;
    }

    @Cacheable(value = "#groupId")
    @Override
    public Group getById(Long groupId) {
        Group group = super.getById(groupId);
        if (Objects.isNull(group)) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在");
        }
        if (group.getDeleted()) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组'" + group.getName() + "'已解散");
        }
        return group;
    }

    @Override
    public List<GroupVO> findGroups() {
        UserSession session = SessionContext.getSession();
        // 查询当前用户的群id列表
        List<GroupMember> groupMembers = groupMemberService.findByUserId(session.getUserId());
        // 一个月内退的群可能存在退群前的离线消息,一并返回作为前端缓存
        groupMembers.addAll(groupMemberService.findQuitInMonth(session.getUserId()));
        if (groupMembers.isEmpty()) {
            return new LinkedList<>();
        }
        // 拉取群列表
        List<Long> ids = groupMembers.stream().map((GroupMember::getGroupId)).collect(Collectors.toList());
        LambdaQueryWrapper<Group> groupWrapper = Wrappers.lambdaQuery();
        groupWrapper.in(Group::getId, ids);
        List<Group> groups = this.list(groupWrapper);
        // 转vo
        return groups.stream().map(g -> {
            GroupVO vo = BeanUtils.copyProperties(g, GroupVO.class);
            GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get();
            vo.setAliasName(member.getAliasName());
            vo.setRemark(member.getRemark());
            vo.setQuit(member.getQuit());
            return vo;
        }).collect(Collectors.toList());
    }

    @Override
    public void invite(GroupInviteVO vo) {
        UserSession session = SessionContext.getSession();
        Group group = this.getById(vo.getGroupId());
        if (Objects.isNull(group)) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
        }
        GroupMember member = groupMemberService.findByGroupAndUserId(vo.getGroupId(), session.getUserId());
        if (Objects.isNull(group) || member.getQuit()) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不在群聊中,邀请失败");
        }
        // 群聊人数校验
        List<GroupMember> members = groupMemberService.findByGroupId(vo.getGroupId());
        long size = members.stream().filter(m -> !m.getQuit()).count();
        if (vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊人数不能大于" + Constant.MAX_GROUP_MEMBER + "人");
        }
        // 找出好友信息
//        List<Friend> friends = friendsService.findFriendByUserId(session.getUserId());
//        List<Friend> friendsList = vo.getFriendIds().stream().map(id -> friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get())
//                .collect(Collectors.toList());
//        if (friendsList.size() != vo.getFriendIds().size()) {
//            throw new GlobalException(ResultCode.PROGRAM_ERROR, "部分用户不是您的好友，邀请失败");
//        }

        //根据好友Id查询好友信息
        List<UserVO> userVOS = userService.queryUsersByIds(vo.getFriendIds());
        // 批量保存成员数据
        List<GroupMember> groupMembers = userVOS.stream().map(userVO -> {
//            Optional<GroupMember> optional = members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst();
//            GroupMember groupMember = optional.orElseGet(GroupMember::new);
            GroupMember groupMember = new GroupMember();
            groupMember.setGroupId(vo.getGroupId());
            groupMember.setUserId(userVO.getId());
            groupMember.setAliasName(userVO.getNickName());
            groupMember.setRemark(group.getName());
            groupMember.setHeadImage(userVO.getHeadImage());
            groupMember.setCreatedTime(new Date());
            groupMember.setQuit(false);
            return groupMember;
        }).collect(Collectors.toList());
        if (!groupMembers.isEmpty()) {
            groupMemberService.saveOrUpdateBatch(group.getId(), groupMembers);
        }
        // 推送进入群聊消息
        List<Long> userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId());
        String memberNames = groupMembers.stream().map(GroupMember::getAliasName).collect(Collectors.joining(","));
        String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames);
        this.sendTipMessage(vo.getGroupId(), userIds, content);
        log.info("邀请进入群聊，群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), vo.getFriendIds());
    }

    @Override
    public List<GroupMemberVO> findGroupMembers(Long groupId) {
        List<GroupMember> members = groupMemberService.findByGroupId(groupId);
        List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
        List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
        return members.stream().map(m -> {
            GroupMemberVO vo = BeanUtils.copyProperties(m, GroupMemberVO.class);
            vo.setOnline(onlineUserIds.contains(m.getUserId()));
            return vo;
        }).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());
    }

    @Override
    public void invite(GroupAccidentInviteVO vo, boolean flag) {
        String accidentId = vo.getAccidentId();
        List<Long> friendIds = vo.getFriendIds();
        if (CollectionUtils.isEmpty(friendIds)) {
            return;
        }
        Group group = this.getOne(new LambdaQueryWrapper<Group>().eq(Group::getAccidentId, accidentId));
        if (Objects.isNull(group)) {
            throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
        }
        List<User> users = userService.list(new LambdaQueryWrapper<User>().in(User::getId, friendIds));
        if (flag){
            // 获取通话日志
            PhgLog callPhgLog = getCallPhgLog(friendIds, accidentId,users);
            phgLogService.save(callPhgLog);
        }
        //查询群成员
        List<GroupMember> groupMemberList = groupMemberService.list(new LambdaQueryWrapper<GroupMember>()
                .eq(GroupMember::getGroupId, group.getId()));
        List<Long> existUserIds = groupMemberList.stream().map(GroupMember::getUserId).collect(Collectors.toList());
        // 未在群聊中的用户Id
        List<Long> missingIds = friendIds.stream()
                .filter(id -> groupMemberList.stream().noneMatch(member -> member.getUserId().equals(id)))
                .collect(Collectors.toList());
        if (CollectionUtils.isEmpty(missingIds)){
            redisTemplate.convertAndSend("sys_msg", Arrays.asList(friendIds));
            return;
        }
        //根据好友Id查询好友信息
        List<UserVO> userVOS = userService.queryUsersByIds(missingIds);
        // 批量保存成员数据
        List<GroupMember> groupMembers = userVOS.stream().map(userVO -> {
//            Optional<GroupMember> optional = members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst();
//            GroupMember groupMember = optional.orElseGet(GroupMember::new);
            GroupMember groupMember = new GroupMember();
            groupMember.setGroupId(group.getId());
            groupMember.setUserId(userVO.getId());
            groupMember.setAliasName(userVO.getNickName());
            groupMember.setRemark(group.getName());
            groupMember.setHeadImage(userVO.getHeadImage());
            groupMember.setCreatedTime(new Date());
            groupMember.setQuit(false);
            return groupMember;
        }).collect(Collectors.toList());
        if (!groupMembers.isEmpty()) {
            groupMemberService.saveOrUpdateBatch(group.getId(), groupMembers);
        }

        // 推送进入群聊消息
        List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
        String memberNames = groupMembers.stream().map(GroupMember::getAliasName).collect(Collectors.joining(","));
        String content = String.format("'%s'加入了群聊", memberNames);
        // 用户首次进群推送全部消息
        this.sendGroupMessage(group.getId(),  missingIds);
        this.sendTipMessage(group.getId(), userIds, content);
        redisTemplate.convertAndSend("sys_msg", Arrays.asList(userIds));
        log.info("邀请进入群聊，群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), missingIds);
    }

    private void sendGroupMessage(Long groupId, List<Long> missingIds) {
        UserSession session = SessionContext.getSession();
        List<GroupMessage> messages = groupMessageMapper.selectList(new QueryWrapper<GroupMessage>().eq("group_id", groupId));
        for (GroupMessage message : messages) {
            GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class);
            IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
            sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
            sendMessage.setRecvIds(missingIds);
            sendMessage.setData(msgInfo);
            sendMessage.setSendResult(false);
            sendMessage.setSendToSelf(false);
            imClient.sendGroupMessage(sendMessage);
        }
    }

    @Override
    public Long inviteBotMessage(GroupVO group) {
        List<User> users = userService.list(new LambdaQueryWrapper<User>().eq(User::getNickName, "消息通知"));
        Optional<Long> first = users.stream().map(User::getUserId).findFirst();
        if (first.isPresent()){
            Long userId = first.get();
            GroupAccidentInviteVO inviteVO = new GroupAccidentInviteVO();
            inviteVO.setAccidentId(group.getAccidentId());
            inviteVO.setFriendIds(Collections.singletonList(userId));
            this.invite(inviteVO,false);
            return userId;
        }
        return null;
    }

    private void sendTipMessage(Long groupId, List<Long> recvIds, String content) {
        UserSession session = SessionContext.getSession();
        // 消息入库
        GroupMessage message = new GroupMessage();
        message.setContent(content);
        message.setType(MessageType.TIP_TEXT.code());
        message.setStatus(MessageStatus.UNSEND.code());
        message.setSendTime(new Date());
        message.setSendNickName(session.getNickName());
        message.setGroupId(groupId);
        message.setSendId(session.getUserId());
        message.setRecvIds(CommaTextUtils.asText(recvIds));
        groupMessageMapper.insert(message);
        // 推送
        GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class);
        IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
        sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
        if (CollUtil.isEmpty(recvIds)) {
            // 为空表示向全体发送
            List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId);
            sendMessage.setRecvIds(userIds);
        } else {
            sendMessage.setRecvIds(recvIds);
        }
        sendMessage.setData(msgInfo);
        sendMessage.setSendResult(false);
        sendMessage.setSendToSelf(false);
        imClient.sendGroupMessage(sendMessage);
    }

    private @NotNull PhgLog getCallPhgLog(List<Long> uid, String accidentId, List<User> users) {
        PhgLog phgLog = new PhgLog();
        phgLog.setType(LogConstants.TYPE_TEXT);
        phgLog.setForeignKey(accidentId);
        Date nowDate = new Date();
        phgLog.setCreateTime(nowDate);
        phgLog.setUpdateTime(nowDate);
        phgLog.setPrefixKey(LogConstants.KEY_HG_ALARM_RESPONSE_RECORD);
        phgLog.setTitle(LogConstants.TITLE_VIDEO_CALL);
        phgLog.setContent(String.format(LogConstants.CONTENT_VIDEO_CALL,
                DateUtil.format(nowDate,"yyyy-MM-dd HH:mm:ss"), StrUtil.join(",",users.stream().map(User::getNickName).collect(Collectors.toList()))));
        phgLog.setPersonId(StrUtil.join(",",uid));
        return phgLog;
    }
}
